package com.whitehatsec.sentineldataexchange;

import java.io.BufferedReader;
import java.io.Closeable;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.util.Iterator;
import java.util.Map;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSession;

/**
 * This class encapsulates several utility methods for accessing the WhiteHat API.
 *
 * @author August Detlefsen [augustd@codemagi.com]
 */
public class Utils {

	private static boolean verifyHostnames = true;

	public static void setVerifyHostnames(boolean newValue) {
		verifyHostnames = newValue;
	}

	private static void setCookies(Map<String, String> cookies, URLConnection urlConn) throws UnsupportedEncodingException {
		//append the cookies
		if (cookies != null && cookies.size() > 0) {
			StringBuilder builder = new StringBuilder(512);
			for (String name : cookies.keySet()) {
				String value = cookies.get(name);
				builder.append(name).append("=").append(value).append(";"); //.append(URLEncoder.encode(value, "UTF-8")).append(";");
			}
			urlConn.setRequestProperty("Cookie", builder.toString());
		}
	}

	private static String parseParameters(Map<String, String> params) {
		//sanity check 
		if (params == null || params.isEmpty()) {
			return "";
		}

		Iterator i = params.keySet().iterator();
		StringBuilder content = new StringBuilder(512);

		while (i.hasNext()) {
			String param = (String) i.next();
			String value = params.get(param);

			if (Utils.isEmpty(value)) {
				continue;
			}

			content.append(param).append("=").append(encodeUTF8(value));

			if (i.hasNext()) {
				content.append("&");
			}
		}

		return content.toString();
	}

	public static String encodeUTF8(String input) {
		try {
			return URLEncoder.encode(input, "UTF-8");
		} catch (UnsupportedEncodingException uee) {
			//we know UTF-8 is supported! This is a STUPID exception! 
		}
		return "";
	}

	private Utils() {
		//this class is not meant to be instantiated
	}

	/**
	 * Posts to a URL and returns the contents as a String.
	 */
	public static String postUrl(String urlString, Map<String, String> params) {
		return postUrl(urlString, params, null);
	}

	/**
	 * Posts to a URL and returns the contents as a String.
	 */
	public static String postUrl(String urlString, Map<String, String> params, Map<String, String> cookies) {
		try {
			URL url = new URL(urlString);
			return postUrl(url, params, cookies);
		} catch (MalformedURLException e) {
			System.out.println("Invalid URL specified");
		}

		return "";
	}

	/**
	 * Posts to an HTTP URL and returns the contents as a String.
	 */
	public static String postUrl(URL url, Map<String, String> params, Map<String, String> cookies) {

		//sanity check
		if (url == null) {
			return "";
		}

		HttpURLConnection urlConn;
		DataOutputStream printout = null;
		BufferedReader input = null;

		try {
			//if we are dealing with http, DO NOT follow redirects
			HttpURLConnection.setFollowRedirects(false);

			// Open URL connection
			urlConn = (HttpURLConnection) url.openConnection();

			//if this is https, should we verify hostnames?
			if (urlConn instanceof HttpsURLConnection && !verifyHostnames) {
				((HttpsURLConnection) urlConn).setHostnameVerifier(new HostnameVerifier() {
					@Override
					public boolean verify(String hostname, SSLSession session) {
						return true;
					}
				});
			}

			//make sure we are using a POST request
			urlConn.setRequestMethod("POST");

			// Let the run-time system (RTS) know that we want input and output
			urlConn.setDoInput(true);
			urlConn.setDoOutput(true);

			// No caching, we want the real thing.
			urlConn.setUseCaches(false);

			// Specify the content type.
			urlConn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");

			//add the cookies to the request
			setCookies(cookies, urlConn);

			//parse the request parameters
			String requestParams = parseParameters(params);

			// Send POST
			printout = new DataOutputStream(urlConn.getOutputStream());

			//add parsed parameters to the request body
			printout.writeBytes(requestParams);

			//send the request to the server
			printout.flush();

			// Get response
			input = new BufferedReader(new InputStreamReader(urlConn.getInputStream()));

			StringBuilder output = new StringBuilder(512);
			String str;
			while (null != ((str = input.readLine()))) {
				output.append(str);
			}

			return output.toString();

		} catch (Exception e) {
			e.printStackTrace(System.err);

		} finally {
			//clean up
			closeStream(printout);
			closeStream(input);

		}

		return "";
	}

	/**
	 * Sends an HTTP GET request to a URL and returns the response as a String
	 *
	 * @param endpoint - The URL of the server.
	 * @param params - The request parameters 
	 * @return - The response from the end point
	 */
	public static String getUrl(String endpoint, Map<String, String> params, Map<String, String> cookies) {
		String result = null;

		BufferedReader input = null;

		// Send a GET request to the servlet
		try {
// Send data
			String urlStr = endpoint;

			String parsedParameters = parseParameters(params);

			if (!isEmpty(parsedParameters)) {
				urlStr += "?" + parsedParameters;
			}

			URL url = new URL(urlStr);
			URLConnection urlConn = url.openConnection();

			//if this is https, should we verify hostnames?
			if (urlConn instanceof HttpsURLConnection && !verifyHostnames) {
				((HttpsURLConnection) urlConn).setHostnameVerifier(new HostnameVerifier() {
					@Override
					public boolean verify(String hostname, SSLSession session) {
						return true;
					}
				});
			}

			//add the cookies to the request
			setCookies(cookies, urlConn);

			// Get the response
			input = new BufferedReader(new InputStreamReader(urlConn.getInputStream()));

			StringBuilder output = new StringBuilder(512);
			String str;
			while (null != ((str = input.readLine()))) {
				output.append(str);
			}

			return output.toString();

		} catch (Exception e) {
			e.printStackTrace(System.err);

		} finally {
			closeStream(input);
		}

		return result;
	}

	/**
	 * Sends an HTTP GET request to a URL and returns the response as a String
	 *
	 * @param endpoint - The URL of the server.
	 * @param params - The request parameters
	 * @return - The response from the end point
	 */
	public static InputStream getUrlAsStream(String endpoint, Map<String, String> params, Map<String, String> cookies) {
		String result = null;

		// Send a GET request to the servlet
		try {
			String urlStr = endpoint;

			String parsedParameters = parseParameters(params);

			if (!isEmpty(parsedParameters)) {
				urlStr += "?" + parsedParameters;
			}

			URL url = new URL(urlStr);
			URLConnection urlConn = url.openConnection();

			//if this is https, should we verify hostnames?
			if (urlConn instanceof HttpsURLConnection && !verifyHostnames) {
				((HttpsURLConnection) urlConn).setHostnameVerifier(new HostnameVerifier() {
					@Override
					public boolean verify(String hostname, SSLSession session) {
						return true;
					}
				});
			}

			//add the cookies to the request
			setCookies(cookies, urlConn);

			//return the response
			return urlConn.getInputStream();

		} catch (Exception e) {
			e.printStackTrace(System.err);

		}

		return null;
	}

	/**
	 * Determines if a string is null or empty
	 *
	 * @param value  string to test
	 * @return       <code>true</code> if the string is empty or null;
	 *               <code>false</code> otherwise
	 */
	public static boolean isEmpty(String value) {
		return (value == null || value.trim().length() == 0);
	}

	/**
	 * Closes a Stream
	 */
	public static void closeStream(Closeable s) {
		try {
			if (s != null) {
				s.close();
			}
			s = null;
		} catch (IOException ioe) {
			//no-op
		}
	}

	/**
	 * Returns the extension portion of a WhiteHat vulnerability URL string.
	 * For example,
	 *    <code>getExtension("moth.wh.lan/php-ids/w3af/audit/xss/simple_xss.php")</code>
	 * would return "php"
	 *
	 * @param filename   The full vulnerability URL from which to extract the extension
	 * @return String    The file extension, or "" if none was found
	 */
	public static String getExtension(String input) {
		int index = input.lastIndexOf(".");

		return (index > 0) ? input.substring(index + 1) : "";
	}

	/**
	 * Returns the path portion of a WhiteHat vulnerability URL string. For example,
	 *    <code>getPath("moth.wh.lan/php-ids/w3af/audit/xss/simple_xss.php")</code>
	 * would return "/php-ids/w3af/audit/xss/"
	 */
	public static String getPath(String input) {
		int startIndex = input.indexOf(System.getProperty("file.separator"));
		int endIndex = input.lastIndexOf(System.getProperty("file.separator"));

		if (startIndex > 0) {
			if (endIndex > 0) {
				return input.substring(startIndex, endIndex + 1);
			} else {
				return input.substring(startIndex);
			}
		}
		return input;
	}

	/**
	 * Returns the host portion of a WhiteHat vulnerability URL string. For example,
	 *    <code>getHost("moth.wh.lan/php-ids/w3af/audit/xss/simple_xss.php")</code>
	 * would return "moth.wh.lan"
	 */
	public static String getHost(String input) {
		int index = input.indexOf(System.getProperty("file.separator"));

		return (index > 0) ? input.substring(0, index) : input;
	}

	/**
	 * Strips HTML characters (anything between < and >) from the input String
	 *
	 * @param input   The String to strip from
	 * @return String The input String, stripped of any HTML characters
	 */
	public static String stripHtmlRegex(String input) {

		if (Utils.isEmpty(input)) {
			return "";
		}

		//replace <P> with double break
		input = input.replaceAll("(?i)<P.*?>", "\n\n");

		//replace <BR> with single break
		input = input.replaceAll("(?i)<BR.*?>", "\n");

		//replace <UL>, <OL> with break
		input = input.replaceAll("(?i)<UL.*?>", "\n");
		input = input.replaceAll("(?i)<OL.*?>", "\n");
		input = input.replaceAll("(?i)</UL>", "\n\n");
		input = input.replaceAll("(?i)</OL>", "\n\n");

		//replace <LI> with *
		input = input.replaceAll("(?i)<LI.*?>", "\n * ");

		//now strip out any tags
		input = input.replaceAll("<.*?>", "");

		//final pass to strip out any incomplete tags
		input = input.replaceAll("<.*", "");

		//replace some common HTML entities with plain text equivalents
		input = input.replaceAll("&nbsp;", " ");
		input = input.replaceAll("&rsquo;", "'");
		input = input.replaceAll("&lsquo;", "'");
		input = input.replaceAll("&apos;", "'");
		input = input.replaceAll("&#39;", "'");
		input = input.replaceAll("&ndash;", "-");
		input = input.replaceAll("&mdash;", "-");
		input = input.replaceAll("&quot;", "\"");
		input = input.replaceAll("&ldquo;", "\"");
		input = input.replaceAll("&rdquo;", "\"");
		input = input.replaceAll("&amp;", "&");

		return input;
	}

	/**
	 * String replacement
	 *
	 * Keep in mind that
	 * "" != ""
	 * is true
	 *
	 * @param sourceString
	 * @param target
	 * @param replaceWith
	 */
	public static String replace(String sourceString, String target, String replaceWith, boolean recursive) {

		//make sure params are not null
		if (Utils.isEmpty(sourceString) || target == null) {
			return sourceString;
		}

		if (replaceWith == null) {
			replaceWith = "";
		}

		int index = 0;
		int targetLength = target.length();
		int replaceLength = replaceWith.length();

		StringBuilder output = new StringBuilder(sourceString);

		while (true) {
			index = output.indexOf(target, index);

			if (index == -1) {
				break;
			}

			//needs to use StringBuffer
			output.replace(index, index + targetLength, replaceWith);

			if (!recursive) {
				index += replaceLength;
			}
		}

		return output.toString();
	}

	public static String trimRedundantWhitespace(String input) {
		//make sure params are not null
		if (Utils.isEmpty(input)) {
			return input;
		}

		String output = input;
		output = Utils.replace(output, "  ", " ", true);
		output = Utils.replace(output, "\t", "", true);
		output = Utils.replace(output, "\n\n\n", "\n\n", true);
		output = Utils.replace(output, "\n ", "\n", true);

		return output;
	}
}
